import com.amd.aparapi.Kernel;
import com.amd.aparapi.Range;
import java.io.*;
import java.util.Random;


class Point {
	float x, y;
	
	public Point() {
		x = 0;
		y = 0;
		
	}
}

public class Main{
	
	public static void partitionWrite(String filename, int[] x)
			throws IOException {

		PrintWriter out = new PrintWriter(filename);
				
		for (int i = 0; i < x.length; i++) {
			out.write(Integer.toString(x[i]) + "\t");
		}
		
		out.close();		
	}
	
	public static void pointWrite(String filename, Point[] x)
			throws IOException {

		PrintWriter out = new PrintWriter(filename);
				
		for (int i = 0; i < x.length; i++) {
			out.write(Float.toString(x[i].x) + "\t" + Float.toString(x[i].y) + "\n");
		}
		
		out.close();		
	}
	
/*public static int [] mult( final int [] A, final int [] B, final int a, final int b, final int c ) {
	final int [] rezultat = new int [ a * c ];
	Kernel kernel = new Kernel() {
		@Override
		public void run() {
			int k = getGlobalId();
			int linie = k/c;
			int coloana = k%c;
			for ( int i=0; i<b; i++ ) {
				rezultat[k] += A[ linie*b + i ] * B[ i*c + coloana ];
			}
		}
	};
	kernel.setExecutionMode( Kernel.EXECUTION_MODE.CPU );
	kernel.execute( rezultat.length );
	
	return rezultat;
}*/
	
	// public static int [] assign( final Point [] data, final Point [] centroids,
	public static int [] assign( final float[] dataxx, final float[] datayy , final float[] centxx,
			final float[] centyy, final int data_n, final int centroid_n) {
		final int [] partitioned = new int [ data_n ];
		Kernel kernel = new Kernel() {
			@Override public void run() {
				int gid = getGlobalId();
				int centroid_i;
				
				float min_dist = Float.MAX_VALUE;

//				for (data_i=0; data_i<data_n; data_i++) {
				for (centroid_i=0; centroid_i<centroid_n; centroid_i++) {
					
					float x, y;
					
//					x = data[gid].x - centroids[centroid_i].x;
//					y = data[gid].y - centroids[centroid_i].y;
					x = dataxx[gid] - centxx[centroid_i];
					y = datayy[gid] - centyy[centroid_i];

					float dist = x * x + y * y;

					if (dist < min_dist) {
						partitioned[gid] = centroid_i;
						min_dist = dist;
					}
				}
			}
		};
		
		kernel.setExecutionMode( Kernel.EXECUTION_MODE.GPU );
		kernel.execute( partitioned.length );		
//		System.out.println("Execution mode=" + kernel.getExecutionMode());
		kernel.dispose();
		
		return partitioned;
	}


	
	public static void main(String[] _args) {

		int data_n = 1048576;
		int centroid_n = 4;
		int prg_size = 1;

		Point[] data = new Point[data_n];
		Point[] centroids = new Point[centroid_n];
		int[] partitioned = new int[data_n];
		int[] count = new int[centroid_n];
		
		int i, data_i, centroid_i;
				
		Random gen = new Random();
		
//		System.out.println("centroid");
		for (centroid_i=0; centroid_i<centroid_n; centroid_i++) {
			centroids[centroid_i] = new Point();
			
			centroids[centroid_i].x = gen.nextFloat() * 20;
			centroids[centroid_i].y = gen.nextFloat() * 20;
		
//			System.out.println(centroid_i + ": (" + centroids[centroid_i].x + ", " + centroids[centroid_i].y);
		}
		
		try {
			pointWrite("org.centroids.point", centroids);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
//		System.out.println("data");
		for (data_i=0; data_i<data_n; data_i++) {
			data[data_i] = new Point();
			
			data[data_i].x = gen.nextFloat() * 20;
			data[data_i].y = gen.nextFloat() * 20;
			
//			System.out.println(data_i + ": (" + data[data_i].x + ", " + data[data_i].y);
		}
		
		try {
			pointWrite("org.data.point", data);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		
		/** Aparapi Kernel which computes squares of input array elements and populates them in corresponding elements of
		 * output array.
		 **/
		
		/*

			Kernel kernel = new Kernel(){
				@Override public void run() {
	        		int gid = getGlobalId();
		        	squares[gid] = values[gid] * values[gid];
				}	
			};
		
		// Execute Kernel.
		kernel.execute(Range.create(512));
		kernel.dispose();
		
		 */
		
		float[] xx = new float[data_n];
		float[] yy = new float[data_n];
		float[] cx = new float[centroid_n];
		float[] cy = new float[centroid_n];
		
		for (int j=0; j<data_n; j++) {
			xx[j] = data[j].x;
			yy[j] = data[j].y;
		}
		
		for (int k=0; k<centroid_n; k++) {
			cx[k] = centroids[k].x;
			cy[k] = centroids[k].y;
		}

		long sTime = System.currentTimeMillis();
		
		for (i=0; i<prg_size; i++) {
//			System.out.println("#" + i + " round");
	  
			partitioned = assign(xx, yy, cx, cy, data_n, centroid_n);
			
			/*
			
			// Assignment step
			for (data_i=0; data_i<data_n; data_i++) {
				
				float min_dist = Float.MAX_VALUE;
		  
				for (centroid_i=0; centroid_i<centroid_n; centroid_i++) {
					
					Point t = new Point();
					
					t.x = data[data_i].x - centroids[centroid_i].x;
					t.y = data[data_i].y - centroids[centroid_i].y;
				
					float dist = t.x * t.x + t.y * t.y;
			  
					if (dist < min_dist) {
						partitioned[data_i] = centroid_i;
						min_dist = dist;
					}
				}
				
//				System.out.println("#" + data_i + ": (" + partitioned[data_i] + ")");
			}
		
			 */
	  
			// Update step
			// Clear sum buffer and class count
			for (centroid_i = 0; centroid_i< centroid_n; centroid_i++) {
				centroids[centroid_i].x = 0.0f;
				centroids[centroid_i].y = 0.0f;
				count[centroid_i] = 0;
			}

			// 	Sum up and count data for each class
			for (data_i = 0; data_i < data_n; data_i++) {         
				centroids[partitioned[data_i]].x += data[data_i].x;
				centroids[partitioned[data_i]].y += data[data_i].y;
				count[partitioned[data_i]]++;
			}
      
			// Divide the sum with number of class for mean point
			for (centroid_i = 0; centroid_i< centroid_n; centroid_i++) {
				centroids[centroid_i].x /= count[centroid_i];
				centroids[centroid_i].y /= count[centroid_i];
			}
	  
		}
		
		long eTime = System.currentTimeMillis();
		
		try {
			partitionWrite("partition.txt", partitioned);
		} catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		 
		
		// Report target execution mode: GPU or JTP (Java Thread Pool).
		// System.out.println("Execution mode=" + kernel.getExecutionMode());
		long fTime =  eTime - sTime;
		System.out.println("Execution time: " + fTime + " ms");
	}
}
